using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using static PKHeX.Core.AutoMod.APILegality;

namespace PKHeX.Core.AutoMod
{
    /// <summary>
    /// Contains logic to create a <see cref="PKM"/> from its elemental details.
    /// </summary>
    public static class Legalizer
    {
        public static bool EnableEasterEggs { get; set; } = true;

        /// <summary>
        /// Tries to regenerate the <see cref="pk"/> into a valid pkm.
        /// </summary>
        /// <param name="pk">Currently invalid pkm data</param>
        /// <returns>Legalized PKM (hopefully legal)</returns>
        public static PKM Legalize(this PKM pk)
        {
            var tr = TrainerSettings.GetSavedTrainerData(pk.Format);
            return tr.MutateLanguage((LanguageID)pk.Language, (GameVersion)pk.Version).Legalize(pk);
        }

        /// <summary>
        /// Tries to regenerate the <see cref="pk"/> into a valid pkm.
        /// </summary>
        /// <param name="tr">Source/Destination trainer</param>
        /// <param name="pk">Currently invalid pkm data</param>
        /// <returns>Legalized PKM (hopefully legal)</returns>
        public static PKM Legalize(this ITrainerInfo tr, PKM pk)
        {
            var set = new RegenTemplate(pk, tr.Generation);
            var almres = tr.GetLegalFromTemplateTimeout(pk, set);
            if (almres.Status == LegalizationResult.VersionMismatch)
                throw new MissingMethodException("PKHeX and Plugins have a version mismatch");
            return almres.Created;
        }

        /// <summary>
        /// Imports <see cref="sets"/> to a provided <see cref="arr"/>, with a context of <see cref="tr"/>.
        /// </summary>
        /// <param name="tr">Source/Destination trainer</param>
        /// <param name="sets">Set data to import</param>
        /// <param name="arr">Current list of data to write to</param>
        /// <param name="invalidAPISets">Returned list of invalid sets that failed to be generated by this api call</param>
        /// <param name="timedoutSets">Returned list of sets that failed to be generated in the premitted time.</param>
        /// <param name="start">Starting offset to place converted details</param>
        /// <param name="overwrite">Overwrite</param>
        /// <returns>Result code indicating success or failure</returns>
        public static AutoModErrorCode ImportToExisting(
            this SaveFile tr,
            IReadOnlyList<ShowdownSet> sets,
            IList<PKM> arr,
            out List<RegenTemplate> invalidAPISets,
            out List<RegenTemplate> timedoutSets,
            int start = 0,
            bool overwrite = true
        )
        {
            var emptySlots = overwrite
                ? Enumerable.Range(start, sets.Count).Where(set => set < arr.Count).ToList()
                : FindAllEmptySlots(arr, start);
            invalidAPISets = new List<RegenTemplate>();
            timedoutSets = new List<RegenTemplate>();

            if (emptySlots.Count < sets.Count)
                return AutoModErrorCode.NotEnoughSpace;

            var generated = 0;
            for (int i = 0; i < sets.Count; i++)
            {
                var set = sets[i];
                var regen = new RegenTemplate(set, tr.Generation);
                if (set.InvalidLines.Count > 0)
                    return AutoModErrorCode.InvalidLines;

                Debug.WriteLine($"Generating Set: {GameInfo.Strings.Species[set.Species]}");
                var almres = tr.GetLegalFromSet(regen);
                if (almres.Status == LegalizationResult.VersionMismatch)
                    return AutoModErrorCode.VersionMismatch;
                var pk = almres.Created;
                pk.ResetPartyStats();
                pk.SetBoxForm();
                if (almres.Status == LegalizationResult.Failed)
                    invalidAPISets.Add(regen);
                if (almres.Status == LegalizationResult.Timeout)
                    timedoutSets.Add(regen);

                var index = emptySlots[i];
                tr.SetBoxSlotAtIndex(pk, index);
                generated++;
            }

            foreach (var r in timedoutSets)
                Dump(r);
            foreach (var r in invalidAPISets)
                Dump(r, true);
            Debug.WriteLine(
                $"API Generated Sets: {generated - invalidAPISets.Count - timedoutSets.Count}/{sets.Count}, {invalidAPISets.Count} were invalid and {timedoutSets.Count} timed out."
            );
            foreach (var set in invalidAPISets)
                Debug.WriteLine(set.Text);
            return AutoModErrorCode.None;
        }

        public static void Dump(RegenTemplate set, bool invalid = false)
        {
            var msg =
                (
                    invalid
                        ? $"[Invalid] [DateTime: {DateTime.Now}]"
                        : $"[Timeout : {APILegality.Timeout} seconds] [DateTime: {DateTime.Now}]"
                )
                + Environment.NewLine
                + set.Text
                + Environment.NewLine;
            System.IO.File.AppendAllText("error_log.txt", msg);
        }

        /// <summary>
        /// Imports a <see cref="set"/> to create a new <see cref="PKM"/> with a context of <see cref="tr"/>.
        /// </summary>
        /// <param name="tr">Source/Destination trainer</param>
        /// <param name="set">Set data to import</param>
        /// <returns>Legalization Result (hopefully legal)</returns>
        public static AsyncLegalizationResult GetLegalFromSet(
            this ITrainerInfo tr,
            IBattleTemplate set
        )
        {
            var template = EntityBlank.GetBlank(tr);
            if (template.Version == 0)
                template.Version = tr.Game;
            EncounterMovesetGenerator.OptimizeCriteria(template, tr);
            template.ApplySetDetails(set);
            return tr.GetLegalFromSet(set, template);
        }

        /// <summary>
        /// Regenerates the set by searching for an encounter that can generate the template.
        /// </summary>
        /// <param name="tr">Trainer Data that was passed in</param>
        /// <param name="set">Showdown set being used</param>
        /// <param name="template">template PKM to legalize</param>
        /// <returns>Legalization Result</returns>
        private static AsyncLegalizationResult GetLegalFromSet(
            this ITrainerInfo tr,
            IBattleTemplate set,
            PKM template
        )
        {
            if (set is ShowdownSet s)
                set = new RegenTemplate(s, tr.Generation);

            var almres = tr.TryAPIConvert(set, template);
            if (almres.Status == LegalizationResult.Regenerated)
                return almres;

            if (EnableEasterEggs)
            {
                var easteregg = tr.GetEasterEggFromSet(set, template);
                return new AsyncLegalizationResult(easteregg, almres.Status, almres.Traceback);
            }

            return almres;
        }

        private static PKM GetEasterEggFromSet(
            this ITrainerInfo tr,
            IBattleTemplate set,
            PKM template
        )
        {
            var gen = EasterEggs.GetGeneration(template.Species);
            var species = (ushort)EasterEggs.GetMemeSpecies(gen, template);

            template.Species = species;
            var form = template.GetAvailableForm();
            if (form == -1)
                return template;

            template.Form = (byte)form;
            var legalencs = tr.GetRandomEncounter(
                template.Species,
                template.Form,
                set.Shiny,
                false,
                false,
                out var legal
            );
            if (legalencs && legal != null)
                template = legal;
            template.SetNickname(EasterEggs.GetMemeNickname(gen, template));
            return template;
        }

        private static int GetAvailableForm(this PKM pk)
        {
            var species = pk.Species;
            var pi = pk.PersonalInfo;
            var formcount = pi.FormCount;
            if (formcount == 0)
                return -1;

            if (!(pk.SWSH || pk.BDSP || pk.LA))
                return pk.Form;

            static bool IsPresentInGameSWSH(ushort species, byte form) =>
                PersonalTable.SWSH.IsPresentInGame(species, form);
            static bool IsPresentInGameBDSP(ushort species, byte form) =>
                PersonalTable.BDSP.IsPresentInGame(species, form);
            static bool IsPresentInGameLA(ushort species, byte form) =>
                PersonalTable.LA.IsPresentInGame(species, form);
            for (byte f = 0; f < formcount; f++)
            {
                if (pk.LA && IsPresentInGameLA(species, f))
                    return f;

                if (pk.BDSP && IsPresentInGameBDSP(species, f))
                    return f;

                if (pk.SWSH && IsPresentInGameSWSH(species, f))
                    return f;
            }
            return -1;
        }

        /// <summary>
        /// API Legality
        /// </summary>
        /// <param name="tr">trainer data</param>
        /// <param name="set">showdown set to legalize from</param>
        /// <param name="template">pkm file to legalize</param>
        /// <returns>LegalizationResult</returns>
        public static AsyncLegalizationResult TryAPIConvert(
            this ITrainerInfo tr,
            IBattleTemplate set,
            PKM template,
            bool nativeOnly = false
        )
        {
            var almres = tr.GetLegalFromTemplateTimeout(template, set, nativeOnly);
            if (almres.Status != LegalizationResult.Regenerated)
                return almres;

            var pkm = almres.Created;
            var trainer = TrainerSettings.GetSavedTrainerData(pkm, tr);
            pkm.SetAllTrainerData(trainer);
            return new AsyncLegalizationResult(pkm, almres.Status, almres.Traceback);
        }

        /// <summary>
        /// Method to find all empty slots in a current box
        /// </summary>
        /// <param name="data">Box Data of the save file</param>
        /// <param name="start">Starting position for finding an empty slot</param>
        /// <returns>A list of all indices in the current box that are empty</returns>
        private static List<int> FindAllEmptySlots(IList<PKM> data, int start)
        {
            var emptySlots = new List<int>();
            for (int i = start; i < data.Count; i++)
            {
                if (data[i].Species < 1)
                    emptySlots.Add(i);
            }
            return emptySlots;
        }
    }
}
